package phr;

import phr.AccessControlList;
import phr.DataBase;
import phr.Identity;
import phr.Message;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Console;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.sql.SQLException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLSocket;

public class Service extends Thread  {
	private SSLConnection sslDS;
	private SSLConnection sslKS;
	private SSLConnection sslClient;
	private Identity identity = null;
	private boolean isWork = true; 
	private DataBase database;
	private boolean isAuthorized = false;

	public Service(SSLConnection ssl, String PhrIp, int PhrPort, String KsIp, int KsPort) {
		sslDS = new SSLConnection(PhrIp, PhrPort);
		sslKS = new SSLConnection(KsIp, KsPort);
		sslClient = ssl;
		this.database = new DataBase("seokje.iptime.org:3306", "hisp_phr database", "hie", "hie");
	}

	public void run() {
		Message message = null;
		System.out.println("thread created");
		try {
			while (isWork) {
				message = new Message(sslClient.readLine());
				System.out.println("message = " + message.toXml());
				if (message.getMessage().equals("logIn")) {
					logIn(message);					
				}				
				else if (message.getMessage().equals("requestHealthRecord") && isAuthorized) {
					sslClient.writeLine(new Message().setMessage("accepted").toXml());
					loadHealthRecord(message);
				}
				else if (message.getMessage().equals("logoff")) {
					break;
				}
				else {
					sslClient.writeLine(new Message().setMessage("denied").toXml());
				}
				
			}

		} catch (IOException e) {
			;
		} catch (SQLException e) {
			;
		}
		
		try {
			sslClient.close();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

	}


	private void loadHealthRecord(Message message) {
		String patientId = message.getHealthRecordAttribute("id");
		String identityXml = message.getIdentityAttribute("xml");
		try {
			sslDS.connect();
			sslDS.writeLine(new Message().setMessage("requestHealthRecord").setHealthRecord(patientId, null, null, null, null, null).toXml());

			message = new Message(sslDS.readLine());
			System.out.println("received HR from server \n" + message.toXml());
			String privateRecord, publicRecord;
			System.out.println("loading from data base = " + message.toXml());

			if (message.getMessage().equals("found")) {
				AccessControlList acl = new AccessControlList(message.getHealthRecordAttribute("acl"));
				String access = acl.hasAccess(new Identity(identityXml));

				System.out.println("access = " + access);
				if (!access.equals("denied")) {
					String key[] = getKey(patientId);
					privateRecord = decrypt(message.getHealthRecordAttribute("private"), key[0]);
					publicRecord = decrypt(message.getHealthRecordAttribute("public"), key[1]);
					sslClient.writeLine(message.setMessage("authorized").setHealthRecord(null, privateRecord, publicRecord, null, null, null).toXml());
				}
				else {
					sslClient.writeLine(new Message().setMessage("denied").toXml());
				}
			}
			else {
				sslClient.writeLine(new Message().setMessage("failed").toXml());
			}
			sslDS.close();
		}
		catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	private void logIn(Message message) throws IOException, SQLException {
		String id = message.getIdentityAttribute("id");
		String password = message.getIdentityAttribute("password");
		try {
			database.connect();
			database.statement = database.connection.createStatement();
			database.resultSet = database.statement.executeQuery("SELECT * FROM accounts WHERE id='" + id + "'");
			database.resultSet.next();
			String storedPassword = database.resultSet.getString("password");
			String salt = database.resultSet.getString("salt");
			String xml = database.resultSet.getString("xml");

			System.out.println("login phase2");
			byte[] bSalt = string2Bytes(salt);

			String hashedPassword = bytes2String(getHash(password, bSalt));

			if (hashedPassword.equals(storedPassword)) {
				System.out.println("granted");
				sslClient.writeLine(new Message().setMessage("authorized").setIdentity(null, null, null, xml).toXml());
				identity = new Identity(xml);
			}
			else {
				System.out.println("denied");
				sslClient.writeLine(new Message().setMessage("denied").toXml());
				isWork = false;
			}			
		} catch (SQLException e) {
			sslClient.writeLine(new Message().setMessage("failed").toXml());
			isWork = false;
		} finally {
			database.statement.close();
			database.connection.close();
			isAuthorized = true;
		}
	}
	
	private String[] getKey (String id) {
		try {
			sslKS.connect();
			sslKS.writeLine(new Message().setMessage("requestKey")
					.setKey(id, null, null, null).toXml());
			Message message = new Message(sslKS.readLine());
			
			String key[] = new String[2];
			key[0] = message.getKeyAttribute("private");
			key[1] = message.getKeyAttribute("public");
			sslKS.close();
			return key;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			try {sslKS.close();}
			catch (Exception e1) {;}
			return null;
		}		
	}

	private byte[] getHash(String string, byte[] salt) {	
		MessageDigest mda;
		try {
			mda = MessageDigest.getInstance("SHA-512");
			mda.reset();
			mda.update(salt);
			byte[] digest = mda.digest(string.getBytes());
			for (int i = 0; i < 1000; i++) {
				mda.reset();
				digest = mda.digest(digest);
			}

			return digest;

		} catch (NoSuchAlgorithmException e) {
			return null;
		} 
	}

	/*
	 * http://stackoverflow.com/questions/521101/using-sha1-and-rsa-with-java-security-signature-vs-messagedigest-and-cipher
	 */
	private String bytes2String(byte[] bytes) {
		StringBuilder string = new StringBuilder();
		for (byte b: bytes) {
			String hexString = Integer.toHexString(0x00FF & b);
			string.append(hexString.length() == 1 ? "0" + hexString : hexString);
		}
		return string.toString();
	}

	/*
	 * http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
	 */
	public byte[] string2Bytes(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
					+ Character.digit(s.charAt(i+1), 16));
		}
		return data;
	}

	public String encrypt (String data, String key) {
		byte[] bKey = string2Bytes(key);
		SecretKeySpec skeySpec = new SecretKeySpec(bKey, "AES");

		Cipher cipher;
		try {
			cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

			byte[] encrpyted = cipher.doFinal(data.getBytes());

			return bytes2String(encrpyted);


		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;		
	}

	public String decrypt (String data, String key) {
		byte[] bKey = string2Bytes(key);
		SecretKeySpec skeySpec = new SecretKeySpec(bKey, "AES");

		Cipher cipher;
		try {
			cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, skeySpec);

			byte[] decrypted = cipher.doFinal(string2Bytes(data));

			return convertHexToString(bytes2String(decrypted));


		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;		
	}

	/*
	 * http://www.mkyong.com/java/how-to-convert-hex-to-ascii-in-java/
	 */
	public String convertHexToString(String hex){

		StringBuilder sb = new StringBuilder();
		StringBuilder temp = new StringBuilder();

		//49204c6f7665204a617661 split into two characters 49, 20, 4c...
		for( int i=0; i<hex.length()-1; i+=2 ){

			//grab the hex in pairs
			String output = hex.substring(i, (i + 2));
			//convert hex to decimal
			int decimal = Integer.parseInt(output, 16);
			//convert the decimal to character
			sb.append((char)decimal);

			temp.append(decimal);
		}
		return sb.toString();
	}


}
